2W - ALB Controller, External DNS
개요
스터디는 2주차에 이뤄졌으나, 몇 가지 이슈로 인해 내가 직접적으로 실습하고 정리하는 타이밍은 4주차가 됐다.
주차 별 내용 분리가 확실한 편이기 때문에, 한 주차에서 다룬 내용은 확실하게 해당 주차로 정리하도록 한다.
이번에는 EKS에서 활용할 수 있는 각종 네트워크 리소스를 알아본다.
구체적으로는 다음의 두 가지를 알아본다.
- AWS의 로드밸런서와 쿠버네티스 네트워크 리소스(인그레스, 서비스.로드밸런서)를 이어주는 ALB Controller
- 외부 도메인을 쿠버네티스 서비스에 부착할 수 있도록 도와주는 External DNS
이를 통해 간단한 네트워크 지식, 운영 방법을 알게 될 것이다.
사전 지식
AWS Load Balancer Controller
ALB Controller는 ALB, NLB 등의 AWS의 로드밸런서를 클러스터의 서비스와 매핑시켜주는 컨트롤러다.
서비스, 인그레스 오브젝트를 만들 때 어노테이션으로 관련 설정을 넣어주면 이를 반영하여 AWS에 관련 리소스가 만들어지게 된다.
대략적인 동작 원리는 이렇다.
- 서비스가 만들어진다.
- 컨트롤러는 클러스터의 서비스 리소스를 감시하고 있다가 관련한 어노테이션이 붙은 것이 있을 때 작업을 시작한다.
- 설정한 값에 맞게 AWS api를 이용하여 ELB를 만들어준다.
AWS api를 쓰기 때문에 해당 컨트롤러는 반드시 관련 리소스를 제어할 수 있는 적절한 권한을 가지고 있어야만 한다.
타겟 모드
AWS의 로드밸런서는 두 가지 유형의 타겟을 둘 수 있는데, ALB 컨트롤러 역시 이러한 기능을 사용할 수 있다.
Instance mode
흔한 로드밸런서의 동작과 같다.
로드밸런서라는 서비스는 사실 외부의 로드밸런서의 ip로 들어오는 요청에 대해 노드포트 규칙에 대응하게 하는 것밖에 없다.
클러스터로 들어가는 트래픽은 노드 포트를 통해 이뤄지며, 이쪽으로 트래픽을 흘려보내는 것을 외부 로드밸런서에 위임하는 것이 바로 로드밸런서 서비스이다.
instance 모드는 그냥 이 방식에 충실한 모드이다.
트래픽 정책에 대한 설정이 없다면 노드로 들어온 트래픽은 내부 iptables 규칙에 따라 분산된다.
그래서 기껏 로드밸런서가 트래픽을 분산해주는데 클러스터 내부로 와서 또 분산되는 꼴이 된다.
기껏 트래픽을 잘 분산시켰다고 생각했는데 내부적으로 또 분산을 해서 괜히 불필요한 트래픽이 발생할 가능성이 높다.
IP mode
이 방식은 로드밸런서가 파드의 IP에 직빵으로 트래픽을 분산해준다.
딱 봐도 한 단계의 hop이 줄고 트래픽 정리도 훨씬 간결해진다.  이게 가능하려면 파드의 IP를 로드밸런서가 알 수 있어야 하고, 또 노드가 파드의 IP로 바로 접근할 수 있도록 지원을 해줘야 한다. 클러스터 외부에서 파드의 IP로 바로 접근할 수 있어야 한다는 말은? 파드가 클러스터 노드들의 IP 대역을 사용해야 한다는 것, 즉 이전에 봤던 VPC CNI를 사용해야만 사용가능한 방식이다. ### 사용 조건 ALB 컨트롤러를 활용하기 위해서 사전적으로 세팅해야 하는 요소는 다음과 같다. - 파드가 api에 통신을 할 수 있도록 하는 적절한 IAM 설정 - VPC CNI를 쓰고 있어야함 - internal, internet-facing을 구분 짓기 위한 서브넷 태그 설정 - internet-facing으로 인식되는 서브넷은 `kubernetes.io/role/elb` - internal로 인식되는 서브넷은 `kubernetes.io/role/internal-elb` - 이렇게 서브넷에 태그를 붙여두어야 로드밸런서가 만들어질 때 어떤 서브넷으로 연결될지를 지정할 수 있다. ## Readiness Gates흔히 파드의 준비 상태를 판단하기 위해서 readinessProbe를 사용한다.
kind: Pod
apiVersion: v1
metadata:
name: pod
spec:
containers:
- name: aews-websrv
image: k8s.gcr.io/echoserver:1.5
readinessProbe:
httpGet:
path: /
port: 8080
initialDelaySeconds: 3
periodSeconds: 3
이것은 kubelet이 컨테이너에 대해서 실행해주는 헬스체크로, 이를 통해 파드는 준비 상태를 나타낸다.
만약 이 헬스체크에 실패할 경우 해당 파드로 트래픽을 보내던 서비스는 바로 엔드포인트를 지운다.
이를 통해 실제 트래픽의 안정성을 도모할 수 있게 되는 것이다.
가령 어떤 한 파드가 큰 규모의 로직을 처리 중이라 다른 트래픽을 받을 수 없는 상태라면, 이럴 때 헬스체크가 실패하도록 로직을 구성해 해당 파드에 트래픽을 받지 않도록 하는 전략이 유효할 것이다.
그러나 로드밸런서를 사용하는 데에 있어서는 문제가 발생할 수 있다.
가령 파드는 이미 준비됐는데 아직 외부의 로드밸런서는 한창 프로비저닝이 되고 있다던가, 파드가 종료되었음에도 외부 로드밸런서는 아직 해당 파드가 건강하다고 생각하고 트래픽을 보내던가..
전자야 서비스에 있어서 큰 문제가 발생하지 않을 수 있으나, 후자는 실제 서비스의 장애를 일으키게 된다.
즉, 외부 로드밸런서와 내부의 파드의 준비 상태에 대한 명확한 동기화가 제대로 이뤄지게 하기 위한 설정이 필요하다는 말이다.
이때 사용하는 것이 바로 readniess Gates이다.
readiness Gate를 활용하면 로드 밸런서의 헬스체크에 따라 파드의 실질 준비 상태가 정해지기 때문에 훨씬 안전하게 트래픽 대응이 가능하다.
- 로드밸런서가 완전히 프로비저닝되어 헬스체크가 성공할 때까지 명시적으로 대기할 수 있다.
- 실제로 트래픽을 받을 수 없는 상태의 파드를 명확하게 걸러낼 수 있다.
뭐.. 사실 후자의 케이스는 홀로 관리하는 사람의 케이스라면 거의 일어나지 않을 이슈라고 생각한다.
하지만 분업 환경에서는 일어날 가능성은 있는 실수라는 생각이 든다.
이럴 때에도 readiness gate는 분명 도움이 되어줄 것이다.
spec:
readinessGates:
- conditionType: "www.example.com/feature-1"
이런 식으로 파드 스펙에 readinessGates
라는 필드를 작성하면 파드 상태에 해당 타입이 추가되고, 관련한 타입에 책임이 있는 컨트롤러가 외부 로드밸런서의 헬스체크 상태를 통해 준비 상태에 대한 조작을 진행해준다.
이 파드가 클러스터 외부 환경에서까지 구동될 준비가 되었다를 나타내는 지표라고 보면 될 것 같다.
External DNS
쿠버네티스 외부에서 원하는 도메인을 붙일 수 있도록 도와주는 툴.
클러스터 내부에서는 CoreDNS를 통해 내부에서 사용하는 DNS가 만들어진다.
이것은 내부에서만 사용할 수 있는 것이고, 인터넷 상에서 고유한 것도 아니다.
그래서 인터넷 상의 고유한 도메인을 가지고 있을 때, 이 도메인을 클러스터의 네트워크 리소스에 연결하고자 할 때는 이 툴을 사용하면 된다.
다양한 DNS 프로바이더에 대해 설정하는 방법이 나와있다.
클러스터 외부와 통신을 하는 컨트롤러가 설치되기 때문에, 이 친구가 관련한 작업을 할 수 있도록 권한 등의 추가 세팅을 해야 한다.
kubectl annotate service tetris "external-dns.alpha.kubernetes.io/hostname=tetris.$DOMAIN"
사용법은 그냥 어노테이션을 붙이는 방식이다.
실습에서 본격적으로 확인하겠다.
실습 진행
ALB Controller 배포
헬름 및 cli를 통한 설정
이 방법은 처음 내가 시도한 방법으로, 조금 삽질을 거친 과정을 담는다.
이후에는 테라폼을 통해 설정을 진행했다.
helm repo add eks https://aws.github.io/eks-charts
helm repo update
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME
설치 자체는 참 간단하다.
k describe deployments.apps -n kube-system aws-load-balancer-controller
어떤 식으로 배포됐는지도 확인해본다.
컨트롤러 컨테이너에는 인자로 현재 클러스터 네임과, 인그레스 클래스가 될 이름이 들어가있다.
서비스 어카운트는 aws-load-balancer-controller
이고, 이 친구가 aws의 로드밸런서를 만들 수 있는 권한이 있어야만 제대로 동작한다.
그 권한은 IRSA로 세팅되는데, 제대로 설정이 된다면 서비스 어카운트의 어노테이션에 eks.amazonaws.com/role-arn
가 붙어있어야 한다.
자세한 내용은 보안 주차에서 다룬다.
이렇게 인그레스 클래스를 확인할 수 있다.
kubectl describe clusterrolebindings.rbac.authorization.k8s.io aws-load-balancer-controller-rolebinding
kubectl describe clusterroles.rbac.authorization.k8s.io aws-load-balancer-controller-role
해당 어카운트가 어떤 롤을 가지고 있는지도 확인해보자.
네트워크와 관련된 다양한 권한을 가지고 있는 것이 확인된다.
이렇게 클러스터 내부의 리소스를 컨트롤러가 접근하여 각종 작업을 해줄 수 있을 것이다.
그럼 이제 클러스터 외부에 컨트롤러가 조작을 할 수 있도록 IRSA를 세팅해보자.[1]
안타깝게도.. 이 친구들은 폴리시 자체는 문서에서 다운 받을 수 있게 해뒀지만 아예 공식적으로 제공해주지는 않는다.
curl -O https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.11.0/docs/install/iam_policy.json
그래서 이걸 받아서 직접 정책을 만들고, 이걸 롤에 붙여주는 작업을 직접 해줘야 한다.[2]
aws iam create-policy \
--policy-name AWSLoadBalancerControllerIAMPolicy \
--policy-document file://iam_policy.json
정책을 미리 만들어서 올려둔다.
eksctl create iamserviceaccount \
--cluster=my-cluster \
--namespace=kube-system \
--name=aws-load-balancer-controller \
--role-name AmazonEKSLoadBalancerControllerRole \
--attach-policy-arn=arn:aws:iam::111122223333:policy/AWSLoadBalancerControllerIAMPolicy \
--approve
그 다음에 eksctl을 사용할 경우, 이렇게 서비스어카운트에 대해 어떤 롤을 붙여줄지를 지정한다.
이때 해당 서비스어카운트에 연결될 롤과 정책을 한번에 지정해버린다.
직접 서비스 어카운트에 해당 값을 넣어본다.
그러나 vpc id에 접근할 수 없다는 에러가 떴고, 이를 직접적으로 넣어주는 방법을 시도했다.
인스턴스의 IMDSv2를 쓰는 것도 가능하지만, 설정이 되려 복잡해진다고 판단했다.
helm install aws-load-balancer-controller eks/aws-load-balancer-controller -n kube-system --set clusterName=$CLUSTER_NAME --set vpcId=$VPCID --set region=ap-northeast-2
컨트롤러를 담당하는 디플로이먼트에 직빵으로 인자를 수정하는 것도 가능하겠으나, 환경 유지 편의성을 위해 이렇게 세팅했다.
그러나 단순히 이렇게하면 문제가 생기는데, 헬름 차트에서 sa를 만들도록 할 경우 원하는 어노테이션이 제대로 부착되지 않기 때문이다.
그래서 k edit sa
를 해서 직접적으로 붙이면 최소한 이 에러는 해결된다.
그런데 또! 막상 컨트롤러가 잘 돌아간다고 해도 주의해야 한다.
sa가 제대로 반영된 채로 컨트롤러가 동작하지 않으면 이렇게 서비스 쪽에서 에러가 발생한다.
sa가 파드 생성 시점에 한번 볼륨으로 만들어져 마운팅되기에, 알아서 변경이 파드에 반영되지 않는다.
제대로 변경해주면 이렇게 성공한다!
그럼 이제 헬름 설정을 할 때 조금 더 제대로 세팅을 할 수 있게 해보자.
롤을 어노로 붙이려면 이 세팅을 하면 될 것이다.
아니면 보통 대중적으로 하는 방식은, 미리 SA를 직접 커스텀해서 만든 후에 여기에서 create=false를 하는 것이다.
내 생각에는 그냥 여기에서 해당 롤을 어노테이션으로 붙이게 하는 것도 괜찮을 것 같다.
테라폼을 통한 설정
여기에 매번 붙이는 방식은 귀찮으니, 이제 테라폼으로 헬름까지 세팅한다.
locals {
lbc_name_in_aws = "AWSLoadBalancerController"
lbc_name_in_cluster = "aws-load-balancer-controller"
lbc_namespace = "kube-system"
}
provider "helm" {
kubernetes {
config_path = "./kubeconfig"
}
}
resource "helm_release" "lbc" {
name = local.lbc_name_in_cluster
repository = "https://aws.github.io/eks-charts"
chart = local.lbc_name_in_cluster
namespace = local.lbc_namespace
set {
name = "clusterName"
value = module.eks.cluster_name
}
set {
name = "region"
value = data.aws_region.current.name
}
set {
name = "vpcId"
value = module.eks_vpc.vpc_id
}
set {
name = "serviceAccount.annotations.eks\\.amazonaws\\.com/role-arn"
value = aws_iam_role.lbc.arn
}
}
data "http" "lbc" {
url = "https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.11.0/docs/install/iam_policy.json"
}
resource "aws_iam_policy" "lbc" {
name = "${local.lbc_name_in_aws}IAMPolicy"
description = "IAM Policy for AWS Load Balancer Controller"
policy = data.http.lbc.response_body
}
data "aws_iam_policy_document" "lbc" {
statement {
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = [module.eks.oidc_provider_arn]
}
condition {
test = "StringEquals"
variable = "${replace(module.eks.cluster_oidc_issuer_url, "https://", "")}:sub"
values = [
"system:serviceaccount:${local.lbc_namespace}:${local.lbc_name_in_cluster}",
]
}
effect = "Allow"
}
}
resource "aws_iam_role" "lbc" {
name = "${local.lbc_name_in_aws}IAM"
assume_role_policy = data.aws_iam_policy_document.lbc.json
}
resource "aws_iam_role_policy_attachment" "lbc" {
role = aws_iam_role.lbc.name
policy_arn = aws_iam_policy.lbc.arn
}
간단하게 코드를 설명하자면, 일단 문서에 나온 최소 기준으로 json파일을 가져와서 그대로 정책을 만든다.
이후에 롤까지 만드는데, 이때 assume을 할 수 있는 롤을 명시할 때는 해당 롤을 받을 수 있는 주체는 eks 클러스터의 OIDC로부터 인증을 받은 주체만 가능하도록 한다.
이를 체크하는 것이 sts:AssumeRoleWithWebIdentity
이고, identifiers에는 oidc 프로바이더를 명시해주면 된다.
확실치 않은 부분으로, 프로바이더의 arn을 넣어주는데 외부 oidc를 사용할 수 없는 것인지는 잘 모르겠다.
아무튼 이를 통해 프로바이더에게 받은 토큰에 sub 클레임을 확인하여 로드밸런서 컨트롤러의 서비스 어카운트가 가질 값을 가지는지 확인한다.
이를 통과한 주체라면 로밸컨이 가져야 하는 정책을 활용할 수 있게 된다.
또한 헬름 리소스에서는 알아서 서비스 어카운트가 만들어지고, 이 어카운트의 어노테이션에 연결될 롤의 arn을 넣음으로써 IRSA가 성립하게 된다.
서비스.로드밸런서 테스트
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-echo
spec:
replicas: 2
selector:
matchLabels:
app: deploy-websrv
template:
metadata:
labels:
app: deploy-websrv
spec:
terminationGracePeriodSeconds: 0
containers:
- name: aews-websrv
image: k8s.gcr.io/echoserver:1.5
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: svc-nlb-ip-type
annotations:
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: "8080"
service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
spec:
ports:
- port: 80
targetPort: 8080
protocol: TCP
type: LoadBalancer
loadBalancerClass: service.k8s.aws/nlb
selector:
app: deploy-websrv
로드 밸런서로서 쓸 때는 위와 같이 어노테이션을 달아주면 된다.
간단하게 말하자면, 퍼블릭 서브넷에 붙으며 ip 모드로 동작할 수 있도록 만든 것이다.
(아래에서 볼 인그레스와 어노테이션 양식이 다르니 차이를 명심하자.)
서비스에 loadBalancerClass를 지정하는 것이 보이는데, 이 값은 어차피 넣지 않아도 Admission Webhook을 통해 알아서 주입된다.
콘솔이나 k get svc
로 나온 dns 이름을 확인하고 브라우저로 접속한 결과이다.
기본적으로 nlb는 클라이언트 ip를 보존하지 않으므로 request ip가 로드밸런서의 주소가 나온다.
또한 어노테이션으로 일일히 설정해준 덕분에 실제 타겟들의 헬스 상태를 콘솔에서도 체크할 수 있다.
이게 가능한 이유는 IP 모드를 썼기 때문으로, 로드밸런서가 실제 파드의 ip를 알고 헬스체크를 수행해주는 것이다.
NLB=$(k get svc svc-nlb-ip-type | awk 'NR==2 {print $4}')
for i in {1..100}; do curl -s $NLB | grep Hostname ; done | sort | uniq -c | sort -nr
로드밸런싱이 잘 되는지도 테스트해본다.
나온 출력을 정렬하면 고유값을 꺼내는 게 가능해져서 이렇게 편하게 결과를 확인할 수 있다.
k rollout deployment deploy-echo
ip 모드는 실제 파드의 상태를 체크하여 등록 해제와 새로운 등록을 알아서 해준다.
k get targetgroupbindings.elbv2.k8s.aws -o yaml
alb 컨트롤러는 aws의 로드밸런서가 가진 타겟 그룹을 클러스터에도 미러링하여 생성해준다.
그래서 생성된 타겟그룹에 대한 정보를 오브젝트로 이렇게 확인할 수 있다.
간단하게 보안 그룹, vpc id까지도 확인 가능한 것이 보인다.
이걸 활용해 아예 aws에 이미 존재하는 타겟그룹을 클러스터에 만들어서 관리하는 것도 가능하다.
(그림은 다른 실습에서 따왔다)
추가적으로, 만약 ip모드가 아니라 인스턴스 모드로 로드밸런서를 만들면 타겟이 인스턴스로 설정된다.
타겟이 3개인 이유는 현재 실행 중인 노드가 3개이기 때문에 그렇다.
이때는 어디로는 트래픽이 일단 송신되고, 각 인스턴스의 iptables 룰에 의해 알아서 또 트래픽이 라우팅될 것이다.
타겟 그룹 속성 세팅
service.beta.kubernetes.io/aws-load-balancer-target-group-attributes: >
deregistration_delay.timeout_seconds=60,
preserve_client_ip.enabled=true
이번에는 어노테이션으로 타겟 그룹에 대한 속성을 넣어보자.
대상 그룹에 대해 설정할 수 있는 것들은 전부 있다고 보면 된다.[3]
여러 개를 넣을 때는 이렇게 쉼표를 통해서 넣어주면 되는데, 그냥 넣으면 길어지니 >을 통해 folding 형태로 넣어주도록 한다.
이런 변경사항은 alb 컨트롤러가 계속 감시하며 어노테이션의 변경을 인지하고 동적으로 반영해준다.
설정한 값은 문제 생긴 대상을 빠르게 지워지도록 하는 60초 설정, 그리고 실제 client의 ip가 보존되도록 하는 기능이다.
이번에는 우리 집 ip가 나오는 것이 확인된다.
콘솔에서는 관련한 설정들이 제대로 들어간 것을 확인할 수 있다.
레디네스 게이트 활용
k label namespaces default elbv2.k8s.aws/pod-readiness-gate-inject=enabled
ALB 컨트롤러에서 레디네스 게이트는 사용하고자 하는 네임스페이스에 라벨을 달아 설정해주는 방식으로 운영된다.
이 네임스페이스에서 생성되는 서비스에 대해서는 컨트롤러가 알아서 대상이 되는 파드에 레디네스 게이트를 주입시킨다.
해당 네임스페이스에 배치되는 파드들에 readinessGates가 주입되는 것이 보인다.
파드에 readiness gate가 설정되고, 이 값은 실제 로드 밸런서에서 헬스체크가 완료되었을 때 비로소 1이 된다.
apiVersion: apps/v1
kind: Deployment
metadata:
name: deploy-echo-2
spec:
replicas: 1
selector:
matchLabels:
app: deploy-websrv
template:
metadata:
labels:
app: deploy-websrv
spec:
terminationGracePeriodSeconds: 0
containers:
- name: aews-websrv
image: nginx
readinessProbe:
httpGet:
path: /
port: 80
만들어진 서비스에 엔드포인트로 잡히지만, 8080포트로 소켓을 열어두지 않는 임의의 파드를 하나 더 만들었다.
보다시피 이 파드는 ready 상태이나, readiness gate는 계속 실패 상태이다.
마찬가지로 콘솔에서도 헬스체크는 실패 상태이다.
명확하게 파드의 상태를 로드밸런서가 감지하고 있는 것을 알 수 있다.
참고로 인스턴스 모드에서는 이를 쓸 수 없다.
k get ep svc-nlb-ip-type -o yaml
이렇게 레디네스를 설정하면 실제로 서비스에서도 엔드포인트를 준비되지 않았다고 설정해준다.
그래서 외부 로드밸런서가 트래픽을 보내지 않음은 물론, 클러스터 내부에서도 해당 파드로 트래픽을 보내지 않게 된다.
번외 - 레디네스 게이트가 설정되지 않은 상태에서 잘못 설정해보기
번외로, 레디네스 게이트가 설정되지 않은 네임스페이스에서는 보다시피 파드의 ready 상태만 따져서 엔드포인트가 추가되는 것을 볼 수 있다.
레디네스 게이트.. 써야겠지?
k run -ti -n test debug --image nicolaka/netshoot -- zsh
---
for i in {1..100}; do
if curl -s svc-nlb-ip-type.test | grep -q "Hostname"; then
echo "success";
else
echo "fail";
fi;
done | sort | uniq -c | sort -nr
실제로 해당 네임스페이스에서 서비스로 계속 호출을 날려보면..
서비스가 간혹 실패하는 것이 보인다.
인그레스 테스트
k get ingressclass alb -oyaml | yh
로드밸런서 클래스와 다르게, 인그레스는 확실하게 클래스 오브젝트가 존재한다.
spec 하위 필드로 parameterts
필드를 넣음으로써 여러 설정이 가능한데, ALB 컨트롤러에서는 이를 위해 IngressClassParams라는 CRD를 따로 제공한다.
parameters:
apiGroup: k8s.example.com
kind: IngressParameters
name: external-lb
세팅할 때는 이런 식으로 넣어주면 되는데, 현재 기본 클래스에는 없는 것이 보인다.
아래 추가적으로 이에 대한 실습을 진행할 것이다.
그런데 재밌는 게, 사용도 하지 않을 거면서 기본으로 ingressclass params도 만들어 둔 게 보인다.
이제 기본적인 실습을 해보자.
apiVersion: v1
kind: Namespace
metadata:
name: game-2048
---
apiVersion: apps/v1
kind: Deployment
metadata:
namespace: game-2048
name: deployment-2048
spec:
selector:
matchLabels:
app.kubernetes.io/name: app-2048
replicas: 2
template:
metadata:
labels:
app.kubernetes.io/name: app-2048
spec:
containers:
- image: public.ecr.aws/l6m2t8p7/docker-2048:latest
imagePullPolicy: Always
name: app-2048
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
namespace: game-2048
name: service-2048
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
type: NodePort
selector:
app.kubernetes.io/name: app-2048
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
namespace: game-2048
name: ingress-2048
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: service-2048
port:
number: 80
2048 게임을 배포해보자!
다시 말하지만 인그레스와 로드밸런서는 사용하는 어노테이션 이름이 다르다.
성공적으로 인그레스가 만들어진 것이 보인다.
브라우저로도 정상적으로 들어갈 수 있다.
보다시피 인그레스에 대해서는 alb가 만들어지는 것이 보인다.
인그레스 그룹 세팅
이번에는 여러 인그레스를 하나의 ALB에 묶어본다.
일전에 내가 미리 만들어둔 이미지를 활용해본다.
이 이미지는 자신의 현 버전 정보를 출력하도록 세팅됐다.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: zero4
annotations:
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/group.name: aews
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /4
pathType: Prefix
backend:
service:
name: zero4
port:
number: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: zero5
annotations:
alb.ingress.kubernetes.io/target-type: instance
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/group.name: aews
spec:
ingressClassName: alb
rules:
- http:
paths:
- path: /5
pathType: Prefix
backend:
service:
name: zero5
port:
number: 80
apiVersion: v1
kind: Service
metadata:
name: zero5
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
type: NodePort
selector:
app: zero5
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: zero5
spec:
selector:
matchLabels:
app: zero5
group: aews
replicas: 3
template:
metadata:
labels:
app: zero5
group: aews
spec:
containers:
- image: zerotay/zero-web:0.0.5
imagePullPolicy: Always
name: zero5
---
apiVersion: v1
kind: Service
metadata:
name: zero4
spec:
ports:
- port: 80
targetPort: 80
protocol: TCP
type: NodePort
selector:
app: zero4
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: zero4
spec:
selector:
matchLabels:
app: zero4
group: aews
replicas: 3
template:
metadata:
labels:
app: zero4
group: aews
spec:
containers:
- image: zerotay/zero-web:0.0.4
imagePullPolicy: Always
name: zero4
어노테이션으로 그룹을 넣어주면, 두 인그레스로 보여도 실제 리소스가 만들어질 때는 하나의 alb만이 만들어진다.
그룹 이름을 통해 로드 밸런서가 만들어졌으며, 뒷단에는 인스턴스와 ip 모드를 가진 서비스를 두고 있다.
4, 5를 경로로 설정했는데 둘다 제대로 값이 나오는 것을 볼 수 있다.
IngressClassParams 세팅
각종 설정들을 매번 어노테이션해주는 것은 퍽 귀찮은 일이다.
어노테이션이란 것 자체가 애초에 만드는 시점에 유효성 검증을 하는 것도 어려워서 많은 혼란을 야기한다.
그래서 이걸 통합적으로 관리할 수 있도록 기본 인그레스 클래스에 조금의 설정을 넣자.
위에서 봤듯이 이미 인그레스 클래스지만 파라미터는 없다.
이 양식을 받아서 수정하고, 동시에 IngressClassParams 오브젝트를 만든다.
# Even if you apply this setting, ingress still needs to be set "name", "target-mode" FUCK
apiVersion: elbv2.k8s.aws/v1beta1
kind: IngressClassParams
metadata:
name: default-icp
spec:
scheme: internet-facing
group:
name: aews
loadBalancerAttributes:
- key: routing.http.xff_client_port.enabled
value: "true"
# certificateArn: ['arn:aws:acm:us-east-1:123456789:certificate/test-arn-1','arn:aws:acm:us-east-1:123456789:certificate/test-arn-2']
tags:
- key: org
value: aews
---
apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
annotations:
meta.helm.sh/release-name: aws-load-balancer-controller
meta.helm.sh/release-namespace: kube-system
labels:
app.kubernetes.io/instance: aws-load-balancer-controller
app.kubernetes.io/managed-by: Helm
app.kubernetes.io/name: aws-load-balancer-controller
app.kubernetes.io/version: v2.11.0
helm.sh/chart: aws-load-balancer-controller-1.11.0
name: alb
spec:
controller: ingress.k8s.aws/alb
parameters:
apiGroup: elbv2.k8s.aws
kind: IngressClassParams
name: default-icp
나는 이런 식으로 만들어주었다.
일단 이 클래스를 이용하면 그룹이 무조건 통일된다.
또한 ALB 헤더에 XFF를 추가해 클라의 ip를 보존할 수 있도록 세팅해주었다.
근데 타겟 모드, 도메인 이름은 결국 사용하는 리소스에서 어노테이션을 달긴해야 한다..
최소한 서브 도메인 postfix 관련 세팅이라도 있으면 좀더 세팅이 간편해질텐데.
설정을 적용하니 기존의 인그레스클래스에 parameter가 생기는 것이 보인다.
이 상태로 위에서 사용한 인그레스에서 그룹 관련 주석만 제거하고 다시 실행해본다.
내가 만든 이미지 0.0.6 버전은 XFF의 헤더 값이 있으면 이를 읽고 클라이언트 ip로서 출력한다.
인그레스 그룹 양식 파일을 조금 수정해서 다시 진행해본다.
0.0.4 버전을 0.0.6으로 바꾸고, 어노테이션에 그룹을 없앴다.
6버전에서는 클라이언트 ip로 내 로컬 주소가 나오는 것이 확인된다.
그룹 세팅이 이미 들어가있기에 하나의 로드밸런서에 두 인그레스가 묶였다.
External DNS 활용
마지막으로 dns를 써본다.
이를 위해서는 먼저 route53에 나만의 도메인을 가지고 있어야만 한다.
테라폼 세팅
나는 역시 테라폼을 이용해 세팅을 진행한다.
locals {
domain_name = "zerotay.com."
ext_name_in_cluster = "external-dns"
ext_namespace = "kube-system"
}
data "aws_route53_zone" "this" {
name = local.domain_name
private_zone = false
}
module "external_dns_irsa" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
version = "5.52.2"
role_name = "external-dns"
attach_external_dns_policy = true
external_dns_hosted_zone_arns = [data.aws_route53_zone.this.arn]
force_detach_policies = true
oidc_providers = {
eks = {
provider_arn = data.aws_iam_openid_connect_provider.this.arn
namespace_service_accounts = ["kube-system:external-dns"]
}
}
}
resource "helm_release" "external_dns" {
name = local.ext_name_in_cluster
repository = "https://kubernetes-sigs.github.io/external-dns"
chart = "external-dns"
namespace = local.ext_namespace
values = [
<<-EOF
env:
- name: AWS_DEFAULT_REGION
value: ${data.aws_region.current.name}
provider:
name: aws
policy: upsert-only
serviceAccount:
annotations:
"eks.amazonaws.com/role-arn": ${module.external_dns_irsa.iam_role_arn}
EOF
]
}
세팅은 이렇게 해주었다.
일단 data로 현재 내가 가지고 있는 도메인의 각종 정보를 가져온다.
이후에 irsa를 설정을 해준 후에 헬름으로 배포!
참고로 여기에서 policy가 upsert-only인데, 이것은 클러스터에서 설정된 레코드가 추가, 변경되는 것만 가능하게 한다.
해당 리소스의 삭제는 레코드에 영향을 미치지 못하게 하는 건데, 실제 서비스 상황에서는 이런 설정이 서비스의 안정성을 유지하는데 도움을 줄 것이다.
테스트
성공적으로 컨트롤러가 설치됐다.
k get -n kube-system sa external-dns -o yaml | yh
IRSA 설정도 제대로 들어간 것이 확인된다.
그럼 간단한 워크로드를 배포해서 확인해보자.
apiVersion: apps/v1
kind: Deployment
metadata:
name: tetris
labels:
app: tetris
spec:
replicas: 1
selector:
matchLabels:
app: tetris
template:
metadata:
labels:
app: tetris
spec:
containers:
- name: tetris
image: bsord/tetris
---
apiVersion: v1
kind: Service
metadata:
name: tetris
annotations:
service.beta.kubernetes.io/aws-load-balancer-nlb-target-type: ip
service.beta.kubernetes.io/aws-load-balancer-scheme: internet-facing
service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"
service.beta.kubernetes.io/aws-load-balancer-backend-protocol: "http"
#service.beta.kubernetes.io/aws-load-balancer-healthcheck-port: "80"
spec:
selector:
app: tetris
ports:
- port: 80
protocol: TCP
targetPort: 80
type: LoadBalancer
loadBalancerClass: service.k8s.aws/nlb
기본 앱을 만들었다.
아직 external dns를 적용한 상태는 아니다.
DOMAIN=zerotay.com
kubectl annotate service tetris "external-dns.alpha.kubernetes.io/hostname=tetris.$DOMAIN"
어노테이션을 추가해본다.
적용하더라도 콘솔에서도, 클러스터에서도 해당 사실이 직접적으로 드러나진 않는다.
k -n kube-system logs external-dns...
하지만 설치된 컨트롤러의 로그를 보면 확실하게 동작을 한 것을 확인할 수 있다.
알아서 해당 도메인에 A레코드가 추가되어 질의도 가능해지는 것이 보인다.
실제로도 잘 들어가는 것이 확인된다!
결론
로드밸런서 IP 모드를 활용하며 명확하게 실제 파드의 헬스체크가 가능해지므로 이를 활용하여 서비스의 안정성을 높일 수 있다.
이를 쓰기 위해서는 vpc cni를 써야만 하므로, EKS에서는 가급적 VPC CNI를 쓰도록 하자.
또한 기본적인 로드밸런서를 이용하면 도메인 이름이 매우 길고 어렵게 나오게 되는데, external dns를 이용하면 기존에 소유하고 있는 도메인으로 서비스에 접속되도록 편하게 설정할 수 있다.
이전 글, 다음 글
다른 글 보기
이름 | index | noteType | created |
---|---|---|---|
1W - EKS 설치 및 액세스 엔드포인트 변경 실습 | 1 | published | 2025-02-03 |
2W - 테라폼으로 환경 구성 및 VPC 연결 | 2 | published | 2025-02-11 |
2W - EKS VPC CNI 분석 | 3 | published | 2025-02-11 |
2W - ALB Controller, External DNS | 4 | published | 2025-02-15 |
3W - kubestr과 EBS CSI 드라이버 | 5 | published | 2025-02-21 |
3W - EFS 드라이버, 인스턴스 스토어 활용 | 6 | published | 2025-02-22 |
4W - 번외 AL2023 노드 초기화 커스텀 | 7 | published | 2025-02-25 |
4W - EKS 모니터링과 관측 가능성 | 8 | published | 2025-02-28 |
4W - 프로메테우스 스택을 통한 EKS 모니터링 | 9 | published | 2025-02-28 |
5W - HPA, KEDA를 활용한 파드 오토스케일링 | 10 | published | 2025-03-07 |
5W - Karpenter를 활용한 클러스터 오토스케일링 | 11 | published | 2025-03-07 |
6W - PKI 구조, CSR 리소스를 통한 api 서버 조회 | 12 | published | 2025-03-15 |
6W - api 구조와 보안 1 - 인증 | 13 | published | 2025-03-15 |
6W - api 보안 2 - 인가, 어드미션 제어 | 14 | published | 2025-03-16 |
6W - EKS 파드에서 AWS 리소스 접근 제어 | 15 | published | 2025-03-16 |
6W - EKS api 서버 접근 보안 | 16 | published | 2025-03-16 |
7W - 쿠버네티스의 스케줄링, 커스텀 스케줄러 설정 | 17 | published | 2025-03-22 |
7W - EKS Fargate | 18 | published | 2025-03-22 |
7W - EKS Automode | 19 | published | 2025-03-22 |
8W - 아르고 워크플로우 | 20 | published | 2025-03-30 |
8W - 아르고 롤아웃 | 21 | published | 2025-03-30 |
8W - 아르고 CD | 22 | published | 2025-03-30 |
8W - CICD | 23 | published | 2025-03-30 |
9W - EKS 업그레이드 | 24 | published | 2025-04-02 |
10W - Vault를 활용한 CICD 보안 | 25 | published | 2025-04-16 |
11W - EKS에서 FSx, Inferentia 활용하기 | 26 | published | 2025-04-18 |
11주차 - EKS에서 FSx, Inferentia 활용하기 | 26 | published | 2025-05-11 |
12W - VPC Lattice 기반 gateway api | 27 | published | 2025-04-27 |
관련 문서
이름 | noteType | created |
---|---|---|
AWS Load Balancer Controller | knowledge | 2025-02-12 |